import java.io.*;
import java.net.*;

import java.lang.Thread;

import java.util.Queue;
import java.util.LinkedList;

/* class GroupChatWorkerThread - It implements a thread, which is used to handel the multiple user group chat.
 Every time when a user tries to get Service Number 6 - "Join Group Chat", an object of this class would be 
 created via a thread. Each of this thread has got run() which gets called when the thread.start() is called
 on the thread object.

 I have tried to make seperate classes, so that the functionality can be modularized and thus, debugging becomes easy.

*/
class GroupChatWorkerThread implements Runnable
{
  Socket socket;
  BufferedReader myReader;
  PrintWriter myWriter;
  String myUser = new String("");

  public GroupChatWorkerThread( Socket passedSocket, String user ) throws IOException
  {
    //System.out.println("clientSocket : " + clientSocket);      
    socket = passedSocket;
    myUser = user;
    myReader = new BufferedReader(new InputStreamReader( socket.getInputStream()));
    myWriter = new PrintWriter(new OutputStreamWriter( socket.getOutputStream()));
  }

  public void run()
  {
	String line;
    //System.out.println("Thread Listening on new socket : " + socket);
    
    while(true)   
    {
	 //System.out.println("Entering infinite loop\n");
	
	try{
		String sentText = new String(myReader.readLine());
		if(sentText != null)
		{		
          System.out.println(myUser+" says : "+sentText);
        }else{}

    	}catch( IOException e){}
         catch( NullPointerException ne){}
	}		
  }// End run
}// End class GroupChatWorkerThread()


/*  class KunalServer
    
    This class is used speciafially to handel multi threading in the application. The main SocketServer would handel all
    the incomming requests, as and when it gets an incomming request, it creates an object of class KunalServer. This object
    would have all the functionality to server the client. 

    All 6 services that are implemented in the client, are handeled by this Class. The object of this clas would have access to all
    6 services functionalities. It also maintains various static variables that would help it to maintain the state
    of communication with the client.

    I have tried to use constructor to initialize all the variables to make it very easy to implement.
    The names of the function evidently describe what they do. That would make it very easy to debugg the code.

*/
class KunalServer{
	
	Socket mySocket;
	BufferedReader mySocketReader,myInputReader;
	PrintWriter mySocketWriter;
	
	static String[] accountNameList = new String[10];
	static int accountNameCounter = 0;
	static String[] loggedInUsers = new String[10];
	static int loggedInUsersCounter = 0;
	static String defaultGroups = new String("Kunal's Group");
	static int defaultGroupsCounter = 1;
	static boolean serverSocketSet = false;
	static ServerSocket groupChatServerSocket;
	static String user= new String("");
	
	
	public KunalServer(Socket passedSocket) throws Exception
		{
		    mySocket = new Socket();
		    mySocket = passedSocket;
			mySocketReader = new BufferedReader(new InputStreamReader( mySocket.getInputStream()));
		    mySocketWriter = new PrintWriter(new OutputStreamWriter( mySocket.getOutputStream()));

				
		}// End Constructor KunalServer()
	
	public void acceptLoginRequest() throws Exception
	{
		System.out.println("Accepting Login Request  :");

	    String loginName = new String(mySocketReader.readLine());
	    user = loginName;
	    if(loginName != null)
		{
		   System.out.println("\n");		
	       System.out.println("USER    : \"" + loginName + "\" Connected");
		   accountNameList[accountNameCounter] = loginName;
		   accountNameCounter++;
		   System.out.println("Account : \""+ loginName + "\" created, as part of Login Process !!\n");
		   loggedInUsers[loggedInUsersCounter] = loginName;
		   loggedInUsersCounter++;
	       
	    }else{}
		
		
	}// End acceptLoginRequest()
	
	public void sendServicesToClient() throws Exception
	{
	  mySocketWriter = new PrintWriter(new OutputStreamWriter( mySocket.getOutputStream()));
	  String menu =  "*** Welcome to Live Chat Server ***\n\n"
	                + "   Enter Service Number from the Menu ......\n"
	                + "   1. Connect to Server..\n"
	                + "   2. Login ..\n"
	                + "   3. Create/Add an Account ..\n"
	                + "   4. Get List of Valid Accounts\n"          
	                + "   5. List of Currently LoggedIn Users ..\n"
	                + "   6. Join Group Chat Room ..\n"
	                + "   7. Quit\n"
	                + "Select one of the options and Hit Enter .\n"
	                + "flagLastLine\n";	
	 //System.out.println(menu);
	 mySocketWriter.println(menu);
	 mySocketWriter.flush();	
		
		
	}// End sendMenuToClient()
	
	public int getClientRequest() throws Exception
	{
	   int requestNumber = Integer.parseInt(new String (mySocketReader.readLine()));
	   //System.out.println("Client Requests for service number : " + requestNumber);
	   return requestNumber;
	   	
		
	}// End getClientRequest()

  static public void clearScreen()
  {
	int i = 0;
	for(i=0;i<100;i++)
	{
		System.out.println("\n");
	}

  }// End clearScreen
	
	public void serveTheClient(int serviceNumber) throws Exception
	{
		int service = serviceNumber;
		//System.out.println("Service Case Number" + service);
		int i = 0;
		/*
		  serveTheClient() is the main function which actually caters to the functionality requested by the client.  
		  I have used swith case so as to serve the client with appropriate functionality. I have also setup a protocol
		  of communication between the client ant the server. This would make it very easy to communicate between the client
		  and the server.
		*/
		switch(service)
		{
		    case 1:{ // Login Service
			         System.out.println("--- You have already connected to Server ..");
			         break;   
		           }
		  
		    case 2:{ 	// Login
				         System.out.println("Service Requested :: Login");
				         String user = new String(mySocketReader.readLine());
				         System.out.println(user + " wants to log in.....");
				         loggedInUsers[loggedInUsersCounter] = user;
				         loggedInUsersCounter++;
				         //System.out.println("Exiting the service Login");
				         break;
		           }
		 
		    case 3:{ 
			          	 // Creating an Account
				         System.out.println("Service Requested :: Create/Add an Account..");
				         //mySocketWriter = new PrintWriter(new OutputStreamWriter( mySocket.getOutputStream()));
				         //mySocketWriter.println("Enter Account Name :");
				         //mySocketWriter.flush();
						 String accountName = new String(mySocketReader.readLine());
						 //System.out.println(accountName);
						 accountNameList[accountNameCounter] = accountName;
						 accountNameCounter++;
						 System.out.println("Account : "+ accountName + " created !");
					     break;
		           }
		
		    case 4:{ // List of Valid Accounts
			         System.out.println("Service Requested ::List Created Accounts");
			         
			         String accountNames = new String("\nThere are "+(accountNameCounter) +" Created accounts...\n");
			         for(i=0;i<accountNameCounter;i++)
			           {
				         accountNames += (i+1)+". "+accountNameList[i]+"\n";
				       }
				     accountNames += "There could be duplicate names in Accounts, I have to fix that bug. !!\n";
			         accountNames += "endingFlag";
			         mySocketWriter = new PrintWriter(new OutputStreamWriter(mySocket.getOutputStream()));
			         mySocketWriter.println(accountNames);
			         mySocketWriter.flush();
			         //System.out.println(accountNames);
			         break;
		           }
		
		    case 5:{  // List of Current LoggedIn Users
			         System.out.println("Service Requested ::List LoggedIn Users..");
			         String loggedInUserNames = new String("\n There are "+ loggedInUsersCounter + " loggedin users...\n");
			         for(i=0;i<loggedInUsersCounter;i++)
			         {
				        loggedInUserNames += (i+1)+". "+loggedInUsers[i]+"\n";
		        	 }
		             loggedInUserNames += "There could be duplicate names in the Loggedin User List, I have to fix that bug, !!\n";
		             loggedInUserNames += "endingFlag";
		             mySocketWriter.println(loggedInUserNames);
		             mySocketWriter.flush();
		             //System.out.println(loggedInUserNames);  
			         break;
		           }
		   
		    case 6:{ // Join group Chat Room
			      try{
			       //System.out.println("ServerSocketSer : "+serverSocketSet);
			       if(serverSocketSet != true)
			        { serverSocketSet = true;
			          groupChatServerSocket = new ServerSocket(mySocket.getPort());
			        //System.out.println(groupChatServerSocket);
			
				    Socket groupChatSocket = new Socket();
				    while(true)
			          {  
				         System.out.println("Service Requested:: Group Chat ..");
				         String availableGroups = new String("\nThere are "+ defaultGroupsCounter + " groups...\n");
				         for(i=0;i<defaultGroupsCounter;i++)
				         {
					        availableGroups += (i+1)+". "+defaultGroups+"\n";
				         }
				         
				         //availableGroups += "This implementation has taken 60 % of my time, It was fun doig it\n";
				         //availableGroups += "Try logging in as many as 5 users and all of them should be able to chat at same time\n";
				         availableGroups += "Please enter 1 and press Enter, to get started...\n";
				         availableGroups += "endingFlag";
				         mySocketWriter.println(availableGroups);
				         mySocketWriter.flush(); 


				         int usersGroupNumber = Integer.parseInt(new String(mySocketReader.readLine()));

				         //clearScreen();
				         System.out.println("Starting Group chat for Group ... "+ defaultGroups);
				
			             //System.out.println("\nServer Listening on port # :" + mySocket.getPort());
			             //System.out.println("Sending the port number to the client");

			             mySocketWriter.println(groupChatServerSocket.getLocalPort());
			             mySocketWriter.flush();
			
			             groupChatSocket = groupChatServerSocket.accept();
			             
			
						 GroupChatWorkerThread worker = new GroupChatWorkerThread(groupChatSocket,user);
						 Thread workerThread = new Thread(worker);
						 workerThread.start();
					  }
                 }else{ // When server Socket is already set
	                //System.out.println("Inside else");
					Socket groupChatSocketTwo = new Socket();
					
				    while(true)
			          {  
				         System.out.println("Service Requested :: Group Chat ..");
				         String availableGroups = new String("\nThere are "+ defaultGroupsCounter + " groups...\n");
				         for(i=0;i<defaultGroupsCounter;i++)
				         {
					        availableGroups += (i+1)+". "+defaultGroups+"\n";
				         }
				         availableGroups += "Please enter 1 and press Enter, to get started...\n";
				         availableGroups += "endingFlag";
				         mySocketWriter.println(availableGroups);
				         mySocketWriter.flush(); 


				         int usersGroupNumberTwo = Integer.parseInt(new String(mySocketReader.readLine()));

				         //clearScreen();
				         System.out.println("Starting Group chat for Group ... "+ defaultGroups);

			             //System.out.println("\nServer Listening on port # :" + mySocket.getPort());
			             //System.out.println("Sending the port number to the client");

			             mySocketWriter.println(groupChatServerSocket.getLocalPort());
			             mySocketWriter.flush();

			             groupChatSocketTwo = groupChatServerSocket.accept();


						 GroupChatWorkerThread workerTwo = new GroupChatWorkerThread(groupChatSocketTwo,user);
						 Thread workerThreadTwo = new Thread(workerTwo);
						 workerThreadTwo.start();
                      
                      }// End else
                    }

			         
			       	}catch(BindException e){System.out.println("The Address is already in Use, please wait for some time, or try killing process. Thanks");}
			           catch(Exception e){}
		           }
		          
		    case 7:{ 
			         System.out.println("You have logged out successfully...\n");
			         break;
		           }
			
			default: { // Add some more mechanism to call this function again, so that a correct service can be selected.
				       System.out.println("Invalid Selection :");
			           break;
			         }
			
			
		}// End switch()

	}// End sendClientService()
	
}// End class server()

/*
   class WorkerThread - This class is used to create threads from the main class as and when a connection is obtained from the
   client. Each of the threads in the class would create an object of type server, which would then have access to all methods
   thus would help catering the client.

   In all there are two SocketServers that have been developed in this project. One during the initial communication between the client
   and the server and the second when the user requests for group chat. Eavh of which would require to spawn new threads.


*/

class WorkerThread implements Runnable
{
  Socket mySocket;
  //BufferedReader mySocketReader;
  //PrintWriter mySocketWriter;

  public WorkerThread( Socket passedSocket ) throws IOException
  {
    //System.out.println("clientSocket : " + clientSocket);      
    mySocket = passedSocket;
  }

  public void run()
  {
	 try{
		KunalServer myServer = new KunalServer(mySocket);
		
		myServer.acceptLoginRequest();
		
		boolean clientRequesting = true;
		while(clientRequesting)
		{
		  myServer.sendServicesToClient();
		  int service = myServer.getClientRequest();
		  if(service != 7)
		  {
		    myServer.serveTheClient(service);
		  }else{ clientRequesting = false;}
		}
		
	    }catch(NullPointerException npe){ System.out.println("Client Disconnected");}
	     catch(Exception e){System.out.println(e);}
  		
  }// End run
}// End class workerThread()

/*
   class Server - This is the class with the main method. This is where the execution starts. It has various functions
   that the server on the communication front has to handel. Like for example checking the number of connections.
   Queuing the extra connections. It also maintains a global queue of connections.

*/


public class Server{
	
	static int clientCounter = 0;
	static Queue<Socket> socketConnectionQueue = new LinkedList<Socket>();
	
	public void serveQueuedClients() throws IOException
	{
		System.out.println("Serving Queued Clients");
		
	}// End serveQueuedClients()
	
	
	public static void main(String args[]) throws IOException
	{
		String SERVER_HOST = new String("localhost");
		int SERVER_PORT = 0;
		ServerSocket myServerSocket;
		Socket mySocket = new Socket();
		BufferedReader mySocketReader,myInputReader;
		PrintWriter mySocketWriter;
		
		
		Server myServer = new Server();
				
                

        try{
	        //Initialises all the varibales that would be required...

			System.out.println("Please enter Server port # : ");
			myInputReader = new BufferedReader( new InputStreamReader (System.in));
			SERVER_PORT = Integer.parseInt( new String( myInputReader.readLine() ));
		    //SERVER_PORT = 15687;
		    myServerSocket = new ServerSocket( SERVER_PORT );
		
		    //mySocket = new Socket();
		      while(true)
	          {
	             System.out.println("\nServer Listening on port # :" + SERVER_PORT);
	             mySocket = myServerSocket.accept();
	  
	             mySocketReader = new BufferedReader(new InputStreamReader(mySocket.getInputStream()));
	             mySocketWriter = new PrintWriter(new OutputStreamWriter(mySocket.getOutputStream()));
	
	             String request = new String(mySocketReader.readLine());
	
	             clientCounter++;
				 if(clientCounter < 5 )
				 {
				   mySocketWriter.println("GRANTED");
				   mySocketWriter.flush();
					
				   WorkerThread worker = new WorkerThread(mySocket);
				   Thread workerThread = new Thread(worker);
				   workerThread.start();
				 }
				 else{
                    
   
                    socketConnectionQueue.offer(mySocket);
                    System.out.println(socketConnectionQueue);
					mySocketWriter.println("QUEUED");
					mySocketWriter.flush();
					
					//System.out.println("Calling the queue checker function\n");
					//myServer.serveQueuedClients();
					
					
				}// Tell the client if It was queued in the service request.
		      }
			}catch( IOException e)
			 {
                System.out.println(e);        
             }
		
	}// End main
	
}// End class server()
